import {getContextBefore} from "../line/highlight.js"
import {Pos} from "../line/pos.js"
import {getLine} from "../line/utils_line.js"
import {replaceRange} from "../model/changes.js"
import {Range} from "../model/selection.js"
import {replaceOneSelection} from "../model/selection_updates.js"
import {countColumn, Pass, spaceStr} from "../util/misc.js"

// Indent the given line. The how parameter can be "smart",
// "add"/null, "subtract", or "prev". When aggressive is false
// (typically set to true for forced single-line indents), empty
// lines are not indented, and places where the mode returns Pass
// are left alone.
export function indentLine(cm, n, how, aggressive) {
    let doc = cm.doc, state
    if (how == null) how = "add"
    if (how == "smart") {
        // Fall back to "prev" when the mode doesn't have an indentation
        // method.
        if (!doc.mode.indent) how = "prev"
        else state = getContextBefore(cm, n).state
    }

    let tabSize = cm.options.tabSize
    let line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize)
    if (line.stateAfter) line.stateAfter = null
    let curSpaceString = line.text.match(/^\s*/)[0], indentation
    if (!aggressive && !/\S/.test(line.text)) {
        indentation = 0
        how = "not"
    } else if (how == "smart") {
        indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text)
        if (indentation == Pass || indentation > 150) {
            if (!aggressive) return
            how = "prev"
        }
    }
    if (how == "prev") {
        if (n > doc.first) indentation = countColumn(getLine(doc, n - 1).text, null, tabSize)
        else indentation = 0
    } else if (how == "add") {
        indentation = curSpace + cm.options.indentUnit
    } else if (how == "subtract") {
        indentation = curSpace - cm.options.indentUnit
    } else if (typeof how == "number") {
        indentation = curSpace + how
    }
    indentation = Math.max(0, indentation)

    let indentString = "", pos = 0
    if (cm.options.indentWithTabs)
        for (let i = Math.floor(indentation / tabSize); i; --i) {
            pos += tabSize;
            indentString += "\t"
        }
    if (pos < indentation) indentString += spaceStr(indentation - pos)

    if (indentString != curSpaceString) {
        replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input")
        line.stateAfter = null
        return true
    } else {
        // Ensure that, if the cursor was in the whitespace at the start
        // of the line, it is moved to the end of that space.
        for (let i = 0; i < doc.sel.ranges.length; i++) {
            let range = doc.sel.ranges[i]
            if (range.head.line == n && range.head.ch < curSpaceString.length) {
                let pos = Pos(n, curSpaceString.length)
                replaceOneSelection(doc, i, new Range(pos, pos))
                break
            }
        }
    }
}
